unit Checkers.Camera;

interface

uses
  SysUtils,
  Classes,
  BaseClasses,
  VectorGeometry,
  VectorTypes,
  GLScene,
  GLRenderContextInfo,
  GLSmoothNavigator;

type
//Advanced camera class with limitation of a pitch, developed for interaction
//with InpputManager
//TODO: Correct camera autopositioninig
  TCheckersCamera = class(TGLCamera)
  private
    FDx: Integer;
    FDy: Integer;
    FClickX: Integer;
    FClickY: Integer;
    FMaxAngle: Single;
    FMinAngle: Single;
    FIsMouseOnForm: Boolean;
    FNavigator: TGLSmoothNavigator;
    FShift: TShiftState;
    FNewPos: TVector;
    FManual: Boolean;
    FRotateSpeed: TVector2f;
    FVisualizer: TObject;
    FRound: Integer;
    FRotating: Boolean;
    FForceReverse: Boolean;
    function GetTarget: TGLBaseSceneObject;
    procedure SetTarget(const Value: TGLBaseSceneObject);
    procedure SetIsMouseOnForm(const Value: Boolean);
  public
    constructor Create(AOwner: TComponent); override;
    procedure DoProgress(const progressTime: TProgressTimes); override;
    procedure SetPosition(const AX, AY, AZ: Single;
      const AInstant: Boolean = True);
    procedure Rotate(const APitch, ATurn: Single);
    procedure MouseMove(const AX, AY: Integer; const AShift: TShiftState);
    procedure MouseDown(const AX, AY: Integer; const AShift: TShiftState);
    procedure MouseUp(const AX, AY: Integer; const AShift: TShiftState);
    destructor Destroy; override;
    property IsMouseOnForm: Boolean read FIsMouseOnForm write SetIsMouseOnForm;
    property CameraTarget: TGLBaseSceneObject read GetTarget write SetTarget;
    property Visualizer: TObject write FVisualizer;
    property Rounded: Integer read FRound;
    property Rotating: Boolean read FRotating;
  end;

implementation
{ TCheckersCamera }
uses
  Checkers.InputManager,
  Checkers.Visualizer;

constructor TCheckersCamera.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FManual := False;
  FForceReverse := False;
  FNavigator := TGLSmoothNavigator.Create(Self);
  FNavigator.AngleLock := False;
  FNavigator.AutoUpdateObject := False;
  FNavigator.InvertHorizontalSteeringWhenUpsideDown := True;
  FNavigator.MoveUpWhenMovingForward := True;
  FNavigator.UseVirtualUp := True;
  FNavigator.VirtualUp.AsAffineVector := ZVector;
  FNavigator.MovingObject := Self;
  FNavigator.AdjustDistanceParams.Speed := 0.02;
  FNavigator.AdjustDistanceParams.Inertia := 1000;
  FNavigator.InertiaParams.MovementAcceleration := 7;
  FNavigator.InertiaParams.MovementInertia := 200;
  FNavigator.InertiaParams.MovementSpeed := 20;
  FNavigator.InertiaParams.TurnInertia := 150;
  FNavigator.InertiaParams.TurnSpeed := 40;
  FNavigator.InertiaParams.TurnMaxAngle := 0.5;
  FNavigator.MoveAroundParams.TargetObject := TargetObject;

  FClickX := 0;
  FClickY := 0;
  FDx := 0;
  FDy := 0;
//Camera pitch limitation
  FMaxAngle := 0.125;
  FMinAngle := PI - 1.725;
end;

procedure TCheckersCamera.DoProgress(const progressTime: TProgressTimes);
begin
  inherited;
  if ssRight in FShift then
  begin
    FNavigator.MoveAroundTarget(FClickY - FDy, FClickX - FDx, progressTime.delTaTime)
  end
  else
  begin
    FNavigator.MoveAroundTarget(0, 0, progressTime.delTaTime);
  end;
  FRound := 0;
  if not FManual then
  begin
    FRound := Round(AbsolutePosition[2] * 100);
    FRotating := False;
    if (FRound <> 0) or FForceReverse then
    begin
      FRotating := True;
      FForceReverse := False;
      FRotateSpeed := GetSafeTurnAngle(AbsolutePosition, Up.AsVector, FNewPos, WHmgVector);
      Rotate(0, FRotateSpeed[1] * progressTime.deltaTime * 2);
    end;
  end;
  FClickX := FDx;
  FClickY := FDy;
end;

procedure TCheckersCamera.MouseDown(const AX, AY: Integer;
  const AShift: TShiftState);
begin
  FShift := AShift;
  if ssRight in AShift then
  begin
    FManual := True;
  end;
  FClickX := AX;
  FClickY := AY;
end;

procedure TCheckersCamera.MouseMove(const AX, AY: Integer;
  const AShift: TShiftState);
begin
  FShift := AShift;
  if (ssRight in AShift) and FIsMouseOnForm then
  begin
    FManual := True;
  end;
  FDx := AX;
  FDy := AY;
end;

procedure TCheckersCamera.MouseUp(const AX, AY: Integer;
  const AShift: TShiftState);
var
  vReversed: TVector;
  vl1, vl2: Single;
begin
  if not (ssRight in AShift) then
  begin
    FManual := False;
    vReversed := VectorMake(-FNewPos[0], FNewPos[1], FNewPos[2], 1);
    vl1 := VectorLength(VectorSubtract(AbsolutePosition, FNewPos));
    vl2 := VectorLength(VectorSubtract(AbsolutePosition, vReversed));
    if vl2 < vl1 then
    begin
      FNewPos := vReversed;
      TCheckersVisualizer(FVisualizer).NotifyRevers;
    end;
  end;
end;

procedure TCheckersCamera.Rotate(const APitch, ATurn: Single);
var
  vOriginalT2C, vNormalT2C, vNormalCameraRight, vNewPos: TVector;
  vPitchNow, vDist: Single;
begin
  if Assigned(TargetObject) then
  begin
    // vNormalT2C points away from the direction the camera is looking
    vOriginalT2C := VectorSubtract(AbsolutePosition,
      TargetObject.AbsolutePosition);
    SetVector(vNormalT2C, vOriginalT2C);
    vDist := VectorLength(vNormalT2C);
    NormalizeVector(vNormalT2C);
    // normalRight points to the camera's right
    // the camera is pitching around this axis.
    vNormalCameraRight := VectorCrossProduct(AbsoluteUp, vNormalT2C);
    if VectorLength(vNormalCameraRight) < 0.001 then
      SetVector(vNormalCameraRight, XVector) // arbitrary vector
    else
      NormalizeVector(vNormalCameraRight);
    // calculate the current pitch
    vPitchNow := ArcCos(VectorDotProduct(AbsoluteUp, vNormalT2C));
    vPitchNow := ClampValue(vPitchNow + DegToRad(APitch), FMaxAngle, FMinAngle);
    // create a new vector pointing up and then rotate it down
    // into the new position
    SetVector(vNormalT2C, AbsoluteUp);
    RotateVector(vNormalT2C, vNormalCameraRight, -vPitchNow);
    RotateVector(vNormalT2C, AbsoluteUp, -DegToRad(ATurn));
    ScaleVector(vNormalT2C, vDist);
    vNewPos := VectorAdd(AbsolutePosition, VectorSubtract(vNormalT2C,
      vOriginalT2C));
    if Assigned(Parent) then
      vNewPos := Parent.AbsoluteToLocal(vNewPos);
    Position.AsVector := vNewPos;
    FNewPos[1] := vNewPos[1];
  end;
end;

procedure TCheckersCamera.SetIsMouseOnForm(const Value: Boolean);
begin
  FIsMouseOnForm := Value;
  if not FIsMouseOnForm then
    FManual := False;
end;

procedure TCheckersCamera.SetPosition(const AX, AY, AZ: Single;
  const AInstant: Boolean = True);
begin
  FNewPos := VectorMake(AX, AY, AZ);
  FRotateSpeed := Vector2fMake(0, 0);
  if AInstant then
    Position.SetPoint(AX, AY, AZ);
  FForceReverse := True;
end;

function TCheckersCamera.GetTarget: TGLBaseSceneObject;
begin
  Result := TargetObject;
end;

procedure TCheckersCamera.SetTarget(const Value: TGLBaseSceneObject);
begin
  TargetObject := Value;
  FNavigator.MoveAroundParams.TargetObject := TargetObject;
end;

destructor TCheckersCamera.Destroy;
begin
  FNavigator.Free;
  inherited;
end;


end.
